﻿/*
 * 作者：新生命开发团队（http://www.newlifex.com/）
 * 
 * 版权：版权所有 (C) 新生命开发团队 2002-2014
 * 
 * 修改：海洋饼干（cuteant@outlook.com）
*/

using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Xml;
using System.Xml.Serialization;
using CuteAnt.Reflection;
using CuteAnt.Serialization;

namespace CuteAnt.Xml
{
	/// <summary>Xml写入器</summary>
	public class XmlWriterX : TextWriterBase<XmlReaderWriterSettings>
	{
		#region 属性

		private XmlWriter _Writer;

		/// <summary>写入器</summary>
		public XmlWriter Writer
		{
			get
			{
				if (_Writer == null)
				{
					var settings = new XmlWriterSettings();
					settings.Encoding = Settings.Encoding.TrimPreamble();
					settings.Indent = true;
					_Writer = XmlWriter.Create(Stream, settings);
				}
				return _Writer;
			}
			set
			{
				_Writer = value;
				if (Settings.Encoding != _Writer.Settings.Encoding) Settings.Encoding = _Writer.Settings.Encoding;

				var xw = _Writer as XmlTextWriter;
				if (xw != null && Stream != xw.BaseStream) Stream = xw.BaseStream;
			}
		}

		/// <summary>数据流。更改数据流后，重置Writer为空，以使用新的数据流</summary>
		public override Stream Stream
		{
			get { return base.Stream; }
			set
			{
				if (base.Stream != value) _Writer = null;
				base.Stream = value;
			}
		}

		private String _RootName;

		/// <summary>根元素名</summary>
		public String RootName { get { return _RootName; } set { _RootName = value; } }

		#endregion

		#region 基础元数据

		#region 字符串

		/// <summary>输出字符串字面值,不做编码处理</summary>
		protected override void OnWriteLiteral(string value)
		{
			Writer.WriteString(value);

			AutoFlush();
		}

		#endregion

		#region 时间

		/// <summary>将一个时间日期写入</summary>
		/// <param name="value">数值</param>
		public override void Write(DateTime value)
		{
			Write(XmlConvert.ToString(value, Settings.DateTimeMode));
		}

		#endregion

		/// <summary>写入值类型，只能识别基础类型，对于不能识别的类型，方法返回false</summary>
		/// <param name="value">要写入的对象</param>
		/// <param name="type">要写入的对象类型</param>
		/// <returns>是否写入成功</returns>
		protected override bool WriteValue(object value, Type type)
		{
			if (type == null)
			{
				if (value == null) return false;
				type = value.GetType();
			}

			var code = Type.GetTypeCode(type);

			// XmlConvert特殊处理时间，输出Char时按字符输出，不同于Xml序列化的数字，所以忽略
			if (code != TypeCode.Char && code != TypeCode.String && code != TypeCode.DateTime)
			{
				// XmlConvert也支持这三种值类型
				if (type.CanXmlConvert())
				{
					var str = XmlHelper.XmlConvertToString(value);
					Write(str);
					return true;
				}
			}

			return base.WriteValue(value, type);
		}

		#endregion

		#region 扩展类型

		/// <summary>写对象类型</summary>
		/// <param name="type">类型</param>
		protected override void WriteObjectType(Type type)
		{
			if (Settings.WriteType)
			{
				Writer.WriteAttributeString("type", "http://www.w3.org/2001/XMLSchema-instance", type.Name);
			}
		}

		#endregion

		#region 字典

		/// <summary>写入字典项 </summary>
		/// <param name="value">对象</param>
		/// <param name="keyType">键类型</param>
		/// <param name="valueType">值类型</param>
		/// <param name="index">成员索引</param>
		/// <param name="callback">使用指定委托方法处理复杂数据</param>
		/// <returns>是否写入成功</returns>
		protected override bool WriteKeyValue(DictionaryEntry value, Type keyType, Type valueType, int index, WriteObjectCallback callback)
		{
			// 如果无法取得字典项类型，则每个键值都单独写入类型
			Writer.WriteStartElement("Item");

			{
				Writer.WriteStartElement("Key");
				if (!WriteObject(value.Key, keyType, callback)) return false;
				Writer.WriteEndElement();
			}

			{
				Writer.WriteStartElement("Value");
				if (!WriteObject(value.Value, valueType, callback)) return false;
				Writer.WriteEndElement();
			}

			Writer.WriteEndElement();

			return true;
		}

		#endregion

		#region 枚举

		/// <summary>写入枚举项</summary>
		/// <param name="value">对象</param>
		/// <param name="type">类型</param>
		/// <param name="index">成员索引</param>
		/// <param name="callback">使用指定委托方法处理复杂数据</param>
		/// <returns>是否写入成功</returns>
		protected override bool WriteItem(Object value, Type type, Int32 index, WriteObjectCallback callback)
		{
			Type t = type;
			if (value != null) t = value.GetType();
			String name = t.GetCustomAttributeValue<XmlRootAttribute, String>(true);
			if (name.IsNullOrWhiteSpace() && t != null) name = t.Name;

			Writer.WriteStartElement(name);

			AutoFlush();

			var rs = true;
			if (value != null)
				rs = base.WriteItem(value, type, index, callback);
			else
				Writer.WriteAttributeString("p3", "nil", "http://www.w3.org/2001/XMLSchema-instance", "true");

			AutoFlush();

			Writer.WriteEndElement();

			AutoFlush();

			return rs;
		}

		/// <summary>写入枚举数据，复杂类型使用委托方法进行处理</summary>
		/// <param name="value">对象</param>
		/// <param name="type">类型</param>
		/// <param name="callback">使用指定委托方法处理复杂数据</param>
		/// <returns>是否写入成功</returns>
		public override bool WriteEnumerable(IEnumerable value, Type type, WriteObjectCallback callback)
		{
			if (value == null) return true;

			Type t = value.GetType();
			Type elementType = null;
			if (t.HasElementType) elementType = t.GetElementType();
			Boolean result = false;
			if (typeof(IEnumerable).IsAssignableFrom(elementType))
			{
				if (typeof(IEnumerable).IsAssignableFrom(elementType.GetElementType()))
				{
					elementType = elementType.GetElementType();
					WriteEnumerable(value as IEnumerable, elementType, callback);
				}
				foreach (Object item in value)
				{
					WriteLog("WriteEnumerable", elementType.Name);
					Writer.WriteStartElement("Item");
					result = base.WriteEnumerable(item as IEnumerable, elementType, callback);
					Writer.WriteEndElement();
				}
				return result;
			}

			if (value.GetType().IsArray && value.GetType().GetArrayRank() > 1)
			{
				Array arr = value as Array;
				List<String> lengths = new List<String>();
				for (int j = 0; j < value.GetType().GetArrayRank(); j++)
				{
					lengths.Add(arr.GetLength(j).ToString());
				}
				WriteLengths(String.Join(",", lengths.ToArray()));
			}

			return base.WriteEnumerable(value, type, callback);
		}

		#endregion

		#region 写入对象

		/// <summary>已重载。写入文档的开头和结尾</summary>
		/// <param name="value">要写入的对象</param>
		/// <param name="type">要写入的对象类型</param>
		/// <param name="callback">处理成员的方法</param>
		/// <returns>是否写入成功</returns>
		protected override bool OnWriteObject(object value, Type type, WriteObjectCallback callback)
		{
			if (Depth > 1) return base.OnWriteObject(value, type, callback);

			Type t = type;
			if (t == null && value != null) t = value.GetType();
			String name = RootName;

			if (name.IsNullOrWhiteSpace())
			{
				// 优先采用类型上的XmlRoot特性
				if (type != null) name = type.GetCustomAttributeValue<XmlRootAttribute, String>(true);
				if (name.IsNullOrWhiteSpace() && t != null) name = t.GetCustomAttributeValue<XmlRootAttribute, String>(true);
				if (name.IsNullOrWhiteSpace())
				{
					if (t != null) name = GetName(t);

					if (RootName.IsNullOrWhiteSpace()) RootName = name;
				}
			}

			if (Depth == 1) Writer.WriteStartDocument();
			Writer.WriteStartElement(name);

			AutoFlush();

			Boolean rs = base.OnWriteObject(value, type, callback);

			AutoFlush();

			if (Writer.WriteState != WriteState.Start)
			{
				Writer.WriteEndElement();
				if (Depth == 1) Writer.WriteEndDocument();
			}
			AutoFlush();

			return rs;
		}

		/// <summary>写入对象成员</summary>
		/// <param name="name">成员名字</param>
		/// <param name="value">要写入的对象</param>
		/// <param name="type">要写入的对象类型</param>
		/// <param name="index">成员索引</param>
		/// <param name="callback">处理成员的方法</param>
		/// <returns>是否写入成功</returns>
		protected override bool OnWriteMember(String name, Object value, Type type, Int32 index, WriteObjectCallback callback)
		{
			// 检查成员的值，如果是默认值，则不输出
			//if (value != null && Settings.IgnoreDefault && IsDefault(value, member)) return true;

			// 特殊处理特性，只有普通值类型才能输出为特性
			var isAtt = Settings.MemberAsAttribute && IsAttributeType(type);
			if (isAtt)
				Writer.WriteStartAttribute(name);
			else
				Writer.WriteStartElement(name);

			AutoFlush();

			var rs = base.OnWriteMember(name, value, type, index, callback);

			if (!isAtt) Writer.WriteEndElement();

			AutoFlush();

			return rs;
		}

		/// <summary>写对象引用计数</summary>
		/// <param name="index"></param>
		protected override void OnWriteObjRefIndex(int index)
		{
			if (index > 0) Writer.WriteAttributeString("ObjRef", index.ToString());
		}

		#endregion

		#region 未知对象

		/// <summary>写入未知对象（其它所有方法都无法识别的对象），采用BinaryFormatter或者XmlSerialization</summary>
		/// <param name="value">要写入的对象</param>
		/// <param name="type">要写入的对象类型</param>
		/// <param name="callback">处理成员的方法</param>
		/// <returns>是否写入成功</returns>
		public override bool WriteUnKnown(object value, Type type, WriteObjectCallback callback)
		{
			try
			{
				WriteLog("WriteUnKnown", type.Name);

				//XmlSerializer serial = new XmlSerializer(type);
				//MemoryStream ms = new MemoryStream();
				//serial.Serialize(ms, value);

				//String xml = Encoding.UTF8.GetString(ms.ToArray());
				Write(value.ToXml(Settings.Encoding, "", "", false));

				return true;
			}
			catch
			{
				//只能处理公共类型,Type因其保护级别而不可访问。
			}
			return base.WriteUnKnown(value, type, callback);
		}

		#endregion

		#region 方法

		/// <summary>刷新缓存中的数据</summary>
		public override void Flush()
		{
			Writer.Flush();

			base.Flush();
		}

		/// <summary>写入长度。多维数组用</summary>
		/// <param name="lengths"></param>
		protected override void WriteLengths(string lengths)
		{
			Writer.WriteAttributeString("Lengths", lengths);
		}

		/// <summary>备份当前环境，用于临时切换数据流等</summary>
		/// <returns>本次备份项集合</returns>
		public override IDictionary<String, Object> Backup()
		{
			var dic = base.Backup();
			dic["Writer"] = Writer;

			return dic;
		}

		/// <summary>恢复最近一次备份</summary>
		/// <returns>本次还原项集合</returns>
		public override IDictionary<String, Object> Restore()
		{
			var dic = base.Restore();
			Writer = dic["Writer"] as XmlWriter;

			return dic;
		}

		#endregion

		#region 序列化接口

		/// <summary>写入实现了可序列化接口的对象</summary>
		/// <param name="value">要写入的对象</param>
		/// <param name="type">要写入的对象类型，如果type等于DataTable，需设置DataTable的名称</param>
		/// <param name="callback">处理成员的方法</param>
		/// <returns>是否写入成功</returns>
		public override bool WriteSerializable(object value, Type type, WriteObjectCallback callback)
		{
			if (!typeof(IXmlSerializable).IsAssignableFrom(type)) return base.WriteSerializable(value, type, callback);

			try
			{
				IXmlSerializable xml = value as IXmlSerializable;

				// 这里必须额外写一对标记，否则读取的时候只能读取得到模式而得不到数据
				Boolean b = xml.GetSchema() != null;
				if (b) Writer.WriteStartElement("Data");
				xml.WriteXml(Writer);
				if (b) Writer.WriteEndElement();

				return true;
			}
			catch
			{
				return base.WriteSerializable(value, type, callback);
			}
		}

		#endregion

		#region 获取成员

		/// <summary>获取需要序列化的成员（属性或字段）。在序列化为属性时，需要排列成员，先拍属性，否则会有问题</summary>
		/// <param name="type">指定类型</param>
		/// <param name="value">对象</param>
		/// <returns>需要序列化的成员</returns>
		protected override IObjectMemberInfo[] OnGetMembers(Type type, object value)
		{
			var mis = base.OnGetMembers(type, value);
			if (!Settings.MemberAsAttribute) return mis;

			var list = new List<IObjectMemberInfo>();
			var list2 = new List<IObjectMemberInfo>();
			foreach (var item in mis)
			{
				if (IsAttributeType(item.Type))
					list.Add(item);
				else
					list2.Add(item);
			}
			list.AddRange(list2);
			return list.ToArray();
		}

		#endregion

		#region 辅助方法

		private static String GetName(Type type)
		{
			if (type.HasElementType) return "ArrayOf" + GetName(type.GetElementType());

			var name = type.GetName();
			name = name.Replace("<", "_");

			//name = name.Replace(">", "_");
			name = name.Replace(",", "_");
			name = name.Replace(">", "");
			return name;
		}

		/// <summary>是否可以作为属性写入Xml的类型</summary>
		/// <param name="type">类型</param>
		/// <returns></returns>
		internal static Boolean IsAttributeType(Type type)
		{
			//if (typeof(Type).IsAssignableFrom(type)) return true;

			var code = Type.GetTypeCode(type);
			return code != TypeCode.Object;
		}

		#endregion
	}
}